;;; Bottom level abstraction - an X10 controller object.
;; These map to an X10 controller attached to the computer. They
;; support three operations: open, close and and set-device. Since
;; these correspond to hardware ("things you can kick") that usually
;; can't be used in parallel, they should always be wrapped by an
;; agent.

(ns x10.controllers
  [:use [x10.config :only (*controllers*)]
   [clojure.walk :only (walk)]]
  [:import gnu.io.CommPortIdentifier [com.micheldalal.x10 CM17A]])

;; The tricky part here is that the open should only happen once. We
;; can't open things when we build the *devices* structure, as it will
;; then happen at compile time, which causes the compile to break.
(defprotocol Controller
  "X10 Controller Modules"
  (open [this] "Whatever is needed to open the controller and ports")
  (close [this] "Close & free the port for possible reuse.")
  (set-device [this code unit state]
    "Set the device at address code/unit to state"))

;;; To add a new controller type
;; Write the appropriate defrecored that instantiates the Controller
;; protocol, then add an entry to the *controller-type-map* to map the
;; name used in the config database to the record.
(deftype CM17A-controller [^CM17A controller ^String port delay]
  Controller
  (open [this]
    (when-not (.getSerialPort controller)
      (.setSerialPort controller
		      (.open (CommPortIdentifier/getPortIdentifier port)
			     "Mike's X10 Controller" 2000))
      (Thread/sleep (Integer/parseInt delay)))
    this)
  (close [this] (.close (.getSerialPort controller)) this)
  (set-device [this code unit new-state]
    (.setState controller (first code) (Integer/parseInt unit) new-state)
    (Thread/sleep (Integer/parseInt delay))
    this))


;;; Map from controller type names to code.
;; Note that the keys here must match the types in
;; config/*controllers*.  There should be one entry for each unique
;; controller type. It maps to a function to produce a new controller
;; given a port and delay.
(def *controller-type-map*
  {"CM17A" (fn [port delay] (CM17A-controller. (new CM17A) port delay))})

;;; Map of all available controllers, built from the above and
(def *controller-map*
  (walk (fn [[name module port delay]]
	  {name (agent ((*controller-type-map* module) port delay))})
	#(apply merge %)
	*controllers*))
	